package com.wyt.common.file.video;

import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

/**
 * 环形缓冲流，当流信息过多时，会逐渐覆盖以前的缓存
 * 每次读取时要先调用readPrepare初始化一下，每次都从最老的字节读，一直读取到最新的字节
 */
@Slf4j
public class CycleBufferInputStream extends InputStream {

    /************************************************  环形缓冲区  ***********************************************/
    private ByteBuffer buffer = null;

    private int readPos = 0; //将要读取的位置
    private int writePos = 0; //将要写入的位置
    private boolean isCycle = false; //判断是否已经形成一个环

    public CycleBufferInputStream(int capacity) {
        this.buffer = ByteBuffer.allocateDirect(capacity);
    }

    /**
     * 将字节数组以覆盖的方式放入环形缓冲区
     */
    public void put(byte[] bytes) {
        int used = buffer.capacity() - buffer.position();
        if (used < bytes.length) {
            buffer.put(bytes, 0, used);
            buffer.clear();
            buffer.put(bytes, used, bytes.length - used);
            isCycle = true;
        } else if (used == bytes.length) {
            buffer.put(bytes, 0, used);
            buffer.clear();
            isCycle = true;
        } else {
            buffer.put(bytes, 0, bytes.length);
        }
        writePos = buffer.position();
    }

    /**
     * 定位读取的初始位置（执行inputstream方法前，必须要先调用该方法）
     */
    public void readPrepare() {
        if (buffer.capacity() == writePos || !isCycle) {
            readPos = 0;
        } else {
            readPos = buffer.position() + 1;
        }
    }

    /*************************************************** 输入流传输  ***************************************************/

    /**
     * Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.
     * A subclass must provide an implementation of this method.
     * Returns: the next byte of data, or -1 if the end of the stream is reached.
     * Throws: IOException – if an I/O error occurs.
     */
    @Override
    public int read() throws IOException {
        if (readPos == buffer.capacity()) readPos = 0;
        if (readPos == writePos) return -1;
        int value = buffer.get(readPos++);
        if (value < 0) value = value + 256; //因为文件流里都是正值，而从缓存区读的时候有正有负，所以为了达到文件流传输的效果，这里进行了增补
        return value;
    }

    public static void main(String[] args) throws Exception {
        CycleBufferInputStream stream = new CycleBufferInputStream(1024 * 1024 * 10);

        FileInputStream fis = new FileInputStream("D:\\tmp-data\\1694511149969.h264");
        byte[] bytes = new byte[fis.available()];
        fis.read(bytes);
        stream.put(bytes);

        stream.readPrepare();
        dealVideo(stream);

//        FileInputStream fis2 = new FileInputStream("D:\\tmp-data\\1694511149969.h264");
//        int tmp = 0;
//        while ((tmp = fis2.read()) != -1) {
//            System.out.println("文件字节" + tmp + " 缓冲区字节" + stream.read());
//        }

    }

    /**
     * 从环形缓存环获取最近一帧关键帧字节数组
     * 这里返回的堆外内存，所以注意要及时进行内存释放
     * @param inputStream
     */
    public static ByteBuffer dealVideo(InputStream inputStream)  {
        try {
            int j = 0;
            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);
            ff.start();
            Frame frame = null;
            Frame last = null;
            while ((frame = ff.grabKeyFrame()) != null && frame.image != null) {
                last = frame.clone();
                System.out.println("获取一帧" + j++);
            }
            ff.stop();

            if (last != null) {
                System.out.println("获取最近的一个关键帧");
                ByteBuffer byteBuffer = (ByteBuffer)last.image[0];
                return byteBuffer;
            }
        } catch (Exception e) {
            log.error("提取最近一个关键帧异常\n",e);
        }
        return null;
    }

    public static void dealImage(InputStream inputStream) {
        try {
            Java2DFrameConverter converter = new Java2DFrameConverter();

            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);
            ff.start();
            int j = 0;
            Frame frame = null;
            Frame last = null;
            while ((frame = ff.grabImage()) != null) {
                last = frame.clone();
                System.out.println("获取一张图片" + j++);
            }
            ff.stop();

            if (last != null && last.image != null) {
                System.out.println("存储最后一张图片 ");
                BufferedImage fecthedImage = converter.getBufferedImage(last);
                File screenshotFile = new File("D:\\tmp-data\\", System.currentTimeMillis() + ".jpg");
                ImageIO.write(fecthedImage, "jpg", screenshotFile);
            }

        } catch (Exception e) {
            log.error("提取最近一个图片异常\n",e);
        }
    }

}
